package com.zsj.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void set(String key, Serializable val) {
        redisTemplate.boundValueOps(key).set(val);
    }

    public void setByStringRedisTemplate(String key, String val) {
        stringRedisTemplate.boundValueOps(key).set(val);
    }

    public long getIncrValue(final String key, long increment) {

        ValueOperations<String, Object> operations = redisTemplate.opsForValue();

        return operations.increment(key, increment);
    }

    public void hset(String key, String hashKey, Serializable hashValue) {
        redisTemplate.opsForHash().put(key, hashKey, hashValue);
    }

    public void hsetByStringRedisTemplate(String key, String hashKey, Serializable hashValue) {
        stringRedisTemplate.opsForHash().put(key, hashKey, hashValue);
    }

    public void hsetMap(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    public Long hdel(String key, String[] hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    public Map<Object, Object> hgetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    public Map<Object, Object> hgetAllByStringRedisTemplate(String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    public void zadd(String key, String value, double score) {
        stringRedisTemplate.opsForZSet().add(key, value, score);
    }

    public void zremove(String key, String value) {
        redisTemplate.opsForZSet().remove(key, value);
    }

    public void zremoveRangeByScore(String key, double min, double max) {
        redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    public Map<String, Double> zrangeByScoreWithScores(String key, double start, double end) {
        Set<ZSetOperations.TypedTuple<String>> typedTuples =
                stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, start, end);
        Map<String, Double> result = new HashMap<>();
        typedTuples.stream().forEach(p -> result.put(p.getValue(), p.getScore()));
        return result;
    }

    public long zcount(String key, double start, double end) {
        return redisTemplate.opsForZSet().count(key, start, end);
    }

    public String runLua(String scriptContent, List<String> keys, Object... values) {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(scriptContent);
        redisScript.setResultType(String.class);
        String result = redisTemplate.execute(redisScript, keys, values);
        return result;
    }

    public String runLuaByStringRedisTemplate(String scriptContent, List<String> keys, Object... values) {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(scriptContent);
        redisScript.setResultType(String.class);
        String result = stringRedisTemplate.execute(redisScript, keys, values);
        return result;
    }

    public void set(String key, Serializable val, int expire, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, val, expire, timeUnit);
    }

    public Boolean setNx(String key, Serializable val, int expire, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, val, expire, timeUnit);
    }

    public void setByStringRedisTemplate(String key, String val, int expire, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, val, expire, timeUnit);
    }

    public Serializable get(String key) {
        return (Serializable) redisTemplate.opsForValue().get(key);
    }

    public Serializable getByStringRedisTemplate(String key) {
        return (Serializable) stringRedisTemplate.opsForValue().get(key);
    }


    public void del(String key) {
        redisTemplate.delete(key);
    }

    public boolean tryLock(String key, int expireSec) {
        try {
            return this.setNx(key, "", expireSec, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("设置过期时间时超时", e);
            return false;
        }
    }

    public boolean tryLockWait(String key, int expireSec, int waitSec) {
        long endTime = System.currentTimeMillis() + waitSec * 1000;
        try {
            while (true) {
                if (System.currentTimeMillis() > endTime) {
                    break;
                }
                if (tryLock(key, expireSec)) {
                    return true;
                } else {
                    Thread.sleep(50);
                }
            }
        } catch (Exception e) {
            log.error("锁定资源失败", e);
            return false;
        }
        return false;
    }

}
